package scuts.core;

import scuts.Assert;
import scuts.core.types.Future;
import scuts.core.types.ProgressiveFuture;
import scuts.core.types.Option;
import scuts.core.types.LazyIterator;
import scuts.core.types.Either;


#if !stax
class FlatMapFuture 
{  
  public static function flatMap < S,T > (w:Future<S>, f:S->Future<T>):Future<T>
  {
    var res = new Future();
    w.deliverTo(function (r) 
    {
      var f1 = f(r);
      f1.deliverTo(function (r) res.deliver(r));
      f1.ifCanceled(function () res.cancel());
    });
    w.ifCanceled(function () 
    {
      res.cancel();
    });
    
    return res;
  }
}
#end


class FlatMapProgressiveFuture {
  
  
  public static function flatMap < S,T > (w:ProgressiveFuture<S>, f:S->ProgressiveFuture<T>):ProgressiveFuture<T>
  {
    var res = new ProgressiveFuture();
    w.deliverTo(function (r) 
    {
      var f1 = f(r);
      f1.deliverTo(function (r) res.deliver(r));
      
      f1.onProgress(function (p) {
        res.setProgress(0.5 + p * 0.5);
      });
      
      f1.ifCanceled(function () res.cancel());
    });
    w.ifCanceled(function () 
    {
      res.cancel();
    });
    w.onProgress(function (p) {
      res.setProgress(p * 0.5);
    });
    return res;
  }
}

class FlatMapArrayOption {
  public static function flatMap < S, T > (w:Array<Option<S>>, f:S->Array<Option<T>>):Array<Option<T>>
  {
    var res = [];
    for (i in w) {
      switch (i) {
        case Some(s):
          for (j in f(s)) {
            switch (j) {
              case Some(v): res.push(j);
              case None: // do nothing, filter none
            }
          }
        case None:
      }
    }
    return res;
  }
}


class FlatMapArray 
{
  public static function flatMap < S, T > (w:Array<S>, f:S->Array<T>):Array<T>
  {
    var res = [];
    for (i in w) {
      for (j in f(i)) {
        res.push(j);
      }
    }
    return res;
  }
  
}

class FlatMapOption {
  public static function flatMap < S, T > (o:Option<S>, f:S->Option<T>):Option<T>
  {
    return switch (o) {
      case Some(v): f(v);
      case None: None;
    }
  }
}

class FlatMapEither {
  public static function flatMap < A,B,C,D > (o:Either<A,B>, leftF:A->Either<C, D>, rightF:B->Either<C,D>):Either<C,D>
  {
    return switch (o) {
      case Left(v): leftF(v);
      case Right(v): rightF(v);
    }
  }
}

class FlatMapLazyIterator {
  public static function flatMap < S, T > (w:LazyIterator<S>, f:S->LazyIterator<T>):LazyIterator<T> {
    return function () {
      var inInner = false;
      var outer = null;
      var inner:Iterator<T> = null;
      
      var res = {
        hasNext : null,
        next : function () return inner.next()
      }
      var outerF = null;
      var innerF = null;
      
      outerF = function () {
        if (outer == null) outer = w();
        if (outer.hasNext()) {
          var val = outer.next();
          inner = f(val)();
          res.hasNext = innerF;
          return innerF();
        } else {
          return false;
        }
      }
      innerF = function () {
        if (inner.hasNext()) {
          return true;
        } else {
          return outerF();
        }
      }
      
      res.hasNext = outerF;
      return res;
    }
  }
}

class FlatMapIteratorOption 
{
  public static function flatMap < S, T > (w:Iterator<Option<S>>, f:S->Iterator<Option<T>>):Iterator<Option<T>>
  {
    var inInner = false;
    var outer = w;
    var inner:Iterator<Option<T>> = null;
    var nextVal = null;
    var res = {
      hasNext : null,
      next : function () return { var v = nextVal; nextVal = null; Some(v);}
    }
    var outerF = null;
    var innerF = null;
    outerF = function () {
      while (true) { // while instead of recursion for better performance
        if (outer.hasNext()) {
          var val = outer.next();
          switch (val) {
            case Some(v):
              inner = f(v);
              res.hasNext = innerF;
              return innerF();
            case None:
          }
        } else {
          return false;
        }
      }
      Assert.fail("Should never reach this point");
      return false;
    }
    innerF = function () {
      while (true) { // while instead of recursion for better performance
        if (inner.hasNext()) {
          switch (inner.next()) {
            case Some(v): 
              nextVal = v;
              return true;
            case None:
              
          }
          return true;
        } else {
          return outerF();
        }
      }
      Assert.fail("Should never reach this point");
      return false;
    }
    
    res.hasNext = outerF;
    
    return res;
  }
  
}

class FlatMapIterator 
{

  public static function flatMap < S, T > (w:Iterator<S>, f:S->Iterator<T>):Iterator<T>
  {
    var inInner = false;
    var outer = w;
    var inner:Iterator<T> = null;
    
    var res = {
      hasNext : null,
      next : function () return inner.next()
    }
    var outerF = null;
    var innerF = null;
    outerF = function () {
      
      if (outer.hasNext()) {
        var val = outer.next();
        inner = f(val);
        res.hasNext = innerF;
        return innerF();
      } else {
        return false;
      }
    }
    innerF = function () {
      if (inner.hasNext()) {
        return true;
      } else {
        return outerF();
      }
    }
    
    res.hasNext = outerF;
    
    return res;
  }
  
}